package semant;

import java.util.Enumeration;

import errors.ErrorEchoer;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.Symbol;
import symbol.Table;
import symbol.VarInfo;
import syntaxtree.Absyn;
import syntaxtree.And;
import syntaxtree.ArrayAssign;
import syntaxtree.ArrayLength;
import syntaxtree.ArrayLookup;
import syntaxtree.Assign;
import syntaxtree.Block;
import syntaxtree.BooleanType;
import syntaxtree.Call;
import syntaxtree.ClassDecl;
import syntaxtree.ClassDeclExtends;
import syntaxtree.ClassDeclSimple;
import syntaxtree.Equal;
import syntaxtree.False;
import syntaxtree.Formal;
import syntaxtree.Identifier;
import syntaxtree.IdentifierExp;
import syntaxtree.IdentifierType;
import syntaxtree.If;
import syntaxtree.IntArrayType;
import syntaxtree.IntegerLiteral;
import syntaxtree.IntegerType;
import syntaxtree.LessThan;
import syntaxtree.MainClass;
import syntaxtree.MethodDecl;
import syntaxtree.Minus;
import syntaxtree.NewArray;
import syntaxtree.NewObject;
import syntaxtree.Not;
import syntaxtree.Plus;
import syntaxtree.Print;
import syntaxtree.Program;
import syntaxtree.This;
import syntaxtree.Times;
import syntaxtree.True;
import syntaxtree.VarDecl;
import syntaxtree.While;
import util.List;
import visitor.Visitor;

public class TableGenerator implements Visitor {

	Env env;
	ErrorEchoer err;

	ClassInfo currClass;
	MethodInfo currMethod;

	TableGenerator(Env env, ErrorEchoer err) {
		this.env = env;
		this.err = err;
		err.Reset();
		currClass = null;
		currMethod = null;
	}
	
	/* Preenche a classe com metodos e atributos dos pais e alem disso,
	 * verifica se ha algum ciclo na hierarquia de classes.
	 * Utiliza uma lista de classes ja vistas na hierarquia e caso
	 * o pai da classe atual esteja na lista eh visivel que ha um ciclo. 
	 */
	public void fillParent(Absyn node) {
		
		Table<Symbol> filhos;

		for(Enumeration<Symbol> enumer = env.semPai.keys(); enumer.hasMoreElements(); ) 
		{
			filhos = new Table<Symbol>();
			fillParentRec(enumer.nextElement(), filhos, node);
		}
	}

	private boolean fillParentRec(Symbol filho, Table<Symbol> filhos, Absyn node) {

		boolean ciclo = false;
		Symbol pai = env.semPai.get(filho);
		
		/* Nao tem pai */
		if(pai == null)	
			return false;

		/* Pai esta abaixo na hierarquia, achou ciclo */
		if (filhos.get(pai) != null) 
		{
			err.Error(((Program)node).getClass(filho.toString()), new Object[] {"Ciclo de herança: '" + filho.toString() + "' extends '" + pai.toString() + "'."});
			return true;
		}

		/* se ainda nao achou ciclo se inclui na tabela */
		filhos.put(filho, filho);
		
		/* Chama para o pai */
		ciclo = fillParentRec(env.semPai.get(filho), filhos, node);	
		
		/* Chegou no topo da hierarquia, insere pai como base */
		if (env.classes.get(filho).base == null)
		{
			ClassInfo base = env.classes.get(pai);
			env.classes.get(filho).setBase(base);
		}
		
		return ciclo;
	}

	public void visit(Program node) {
		/* Visita classe principal */
		node.mainClass.accept(this);

		/* Visita lista de classes */
		for ( List<ClassDecl> aux = node.classList; aux != null; aux = aux.tail ) {
			aux.head.accept(this);
		}
		
		/* Preenche as classes com suas bases */
		fillParent(node);
	}

	public void visit(MainClass node) {
		/* Cria simbolos */
		Symbol name = Symbol.symbol(node.className.s);
		Symbol mainMethodSymbol = Symbol.symbol("main");

		/* Cria e popula a class info */
		/* So possui variavel padrao e metodo main */

		ClassInfo mainCl = new ClassInfo(name);

		if (env.classes.put(name, mainCl)) {

			/* Cria metodo main, tipo retorno e parametro padrao */
			IdentifierType mainRetType = new IdentifierType(node.mainArgName.line, 0, "void");
			IdentifierType mainArgType = new IdentifierType(node.mainArgName.line,
					node.mainArgName.row, "String[]");
			VarInfo mainArg = new VarInfo(mainArgType, Symbol.symbol(node.mainArgName.s));
			MethodInfo mainMethodInfo = new MethodInfo(mainRetType, mainMethodSymbol, name);

			/* Adiciona parametro no metodo main */
			if (! mainMethodInfo.addFormal(mainArg))
				err.Error(node, new Object[] {"Nome de parâmetro '" + node.mainArgName + "' já utilizado."});

			/* Adiciona metodo main */
			if (! mainCl.addMethod(mainMethodInfo))
				err.Error(node, new Object[] {"Nome de método 'main' já utilizado."});
		}
		else {
			err.Error(node, new Object[] {"Nome de classe '" + name.toString() + "' já utilizado."});
		}
	}

	public void visit(ClassDeclSimple node) {
		/* Cria simbolos */
		Symbol name = Symbol.symbol(node.name.s);

		currClass = new ClassInfo(name);

		if (env.classes.put(name, currClass)) {

			/* Visita atributos */
			currMethod = null;
			for ( List<VarDecl> aux = node.varList; aux != null; aux = aux.tail )
				aux.head.accept(this);

			/* Visita metodos */
			for ( List<MethodDecl> aux = node.methodList; aux != null; aux = aux.tail )
				aux.head.accept(this);

		}
		else {
			err.Error(node, new Object[] {"Nome de classe '" + name.toString() + "' já utilizado."});	
		}
	}

	public void visit(ClassDeclExtends node) {
		/* Metodo cria classe mas nao coloca informacao da classe pai,
		 * deixa para fazer isto assim que tiver informacao sobre
		 * todas as classes, para que a ClassInfo tenha os metodos e atributos
		 * de todas as classes desde a raiz de heranca.
		 */

		/* Cria simbolos */
		Symbol name = Symbol.symbol(node.name.s);
		Symbol parent = Symbol.symbol(node.superClass.s);

		currClass = new ClassInfo(name);

		if (env.classes.put(name, currClass)) {
			/* Inclui na tabela de herancas */
			env.semPai.put(name, parent); 

			/* Visita atributos */
			currMethod = null;
			for ( List<VarDecl> aux = node.varList; aux != null; aux = aux.tail )
				aux.head.accept(this);

			/* Visita metodos */
			for ( List<MethodDecl> aux = node.methodList; aux != null; aux = aux.tail )
				aux.head.accept(this);		
		} 
		else {
			err.Error(node, new Object[] {"Nome de classe '" + name.toString() + "' já utilizado."});
		}
		
	}

	public void visit(VarDecl node) {

		/* Cria simbolo */
		Symbol name = Symbol.symbol(node.name.s);

		VarInfo varInfo = new VarInfo(node.type, name);

		if (currMethod == null) {
			if (! currClass.addAttribute(varInfo)) {
				err.Error(node, new Object[] {"Nome de atributo '" + name.toString() + 
						"' já utilizado na classe '" + currClass.name.toString() + "'."});
			}
		}
		else {
			if (currMethod.formalsTable.get(name) != null) {
				err.Error(node, new Object[] {"Nome de variável '" + name.toString() + 
						"' já utilizado como parâmetro no método '" 
						+ currMethod.name.toString() + "' da " +
						" classe '" + currClass.name.toString() + "'."});
			}
			else if (! currMethod.addLocal(varInfo)) {
				err.Error(node, new Object[] {"Nome de variável '" + name.toString() + 
						"' já utilizado no método '" + currMethod.name.toString() + "' da " +
						" classe '" + currClass.name.toString() + "'."});
			}
		}

	}

	public void visit(MethodDecl node) {

		/* Cria simbolos */
		Symbol name = Symbol.symbol(node.name.s);

		currMethod = new MethodInfo(node.returnType, name, currClass.name);

		if (currClass.addMethod(currMethod)) {

			/* Visita todos os parametros. Visita primeiro porque vem primeiro. */
			for ( List<Formal> aux = node.formals; aux != null; aux = aux.tail ) {
				aux.head.accept(this);
			}

			/* Visita todas as variaveis locais */
			for ( List<VarDecl> aux = node.locals; aux != null; aux = aux.tail ) {
				aux.head.accept(this);
			}		
		}
		else {
			err.Error(node, new Object[] {"Nome de método '" + name.toString() + 
					"' já utilizado na classe '" + currClass.name.toString() + "'."});
		}

	}

	public void visit(Formal node) {

		/* Cria simbolo */
		Symbol name = Symbol.symbol(node.name.s);

		VarInfo varInfo = new VarInfo(node.type, name);

		if (! currMethod.addFormal(varInfo)) {
			err.Error(node, new Object[] {"Nome de parâmetro '" + name.toString() + 
					"' já utilizado no método '" + currMethod.name.toString() + "' da" +
					" classe '" + currClass.name.toString() + "'."});
		}
	}

	/* Estes nos nao precisam ser visitados nesta passada ja que nao declaram
	 * novos metodos nem novas variaveis.
	 */

	public void visit(IntArrayType node) { }
	public void visit(BooleanType node) { }
	public void visit(IntegerType node) { }
	public void visit(IdentifierType node) { }
	public void visit(Block node) { }
	public void visit(If node) { }
	public void visit(While node) { }
	public void visit(Print node) {	}
	public void visit(Assign node) { }
	public void visit(ArrayAssign node) { }
	public void visit(And node) { }
	public void visit(LessThan node) { }
	public void visit(Equal node) {	}
	public void visit(Plus node) { }
	public void visit(Minus node) { }
	public void visit(Times node) {	}
	public void visit(ArrayLookup node) { }
	public void visit(ArrayLength node) { }
	public void visit(Call node) { }
	public void visit(IntegerLiteral node) { }
	public void visit(True node) { }
	public void visit(False node) { }
	public void visit(This node) { }
	public void visit(NewArray node) { }
	public void visit(NewObject node) { }
	public void visit(Not node) { }
	public void visit(IdentifierExp node) { }
	public void visit(Identifier node) { }

}
